package acr.codec
{
	import acr.codec.events.EncodeCompleteEvent;
	import acr.codec.events.EncodeProgressEvent;
	
	import com.wizhelp.fzlib.FZlib;
	import com.wizhelp.fzlib.ZStream;
	
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.*;
	
	[Event(name=EncodeCompleteEvent.COMPLETE, type="EncodeCompleteEvent")]
	[Event(name=EncodeProgressEvent.PROGRESS, type="EncodeProgressEvent")]
	
	public class AsyncPNGEncoder extends EventDispatcher
	{
		private var width:int;
		private var height:int;
		private var transparent:Boolean;
		
		private var y:int;
		
		private var sourceBitmapData:BitmapData;
		private var sourceByteArray:ByteArray;
		
		private var png:ByteArray;
		private var IHDR:ByteArray;
		private var IDAT:ByteArray;
		
		private var runTime:uint;
		private var haltTime:uint;
		
		private var loopTime:int;
		
		private var crcTable:Array;
		
		private var zStream:ZStream;
		private var zPNG:ByteArray;
		
		public function AsyncPNGEncoder(run:uint=500,stop:uint=500)
		{
			super();
			initializeCRCTable();
			
			runTime=run;
			haltTime=stop;
		}
		
		public function encode(bitmapData:BitmapData):void
	    {
	        internalEncode(bitmapData, bitmapData.width, bitmapData.height,
								  bitmapData.transparent);
	    }
			
		 public function encodeByteArray(byteArray:ByteArray, width:int, height:int,
									transparent:Boolean = true):void
	    {
	        internalEncode(byteArray, width, height, transparent);
	    }
	    

	private function initializeCRCTable():void
	{
        crcTable = [];

        for (var n:uint = 0; n < 256; n++)
        {
            var c:uint = n;
            for (var k:uint = 0; k < 8; k++)
            {
                if (c & 1)
                    c = uint(uint(0xedb88320) ^ uint(c >>> 1));
				else
                    c = uint(c >>> 1);
             }
            crcTable[n] = c;
        }
	}

    /**
	 *  @private
	 */
	private function internalEncode(source:Object, width:int, height:int,
									transparent:Boolean = true):void
    {
    	this.width=width;
    	this.height=height;
    	this.transparent=transparent;
    	
    	zStream=new ZStream();
    	zStream.deflateInit(FZlib.Z_BEST_COMPRESSION);
    	
     	startEncode(source);
        loopEncode();
    }
    
    private function startEncode(source:Object):void
    {
     	// The source is either a BitmapData or a ByteArray.
    	sourceBitmapData = source as BitmapData;
    	sourceByteArray = source as ByteArray;
    	
    	if (sourceByteArray)
    		sourceByteArray.position = 0;
    	
        // Create output byte array
        png = new ByteArray();

        // Write PNG signature
        png.writeUnsignedInt(0x89504E47);
        png.writeUnsignedInt(0x0D0A1A0A);

        // Build IHDR chunk
        IHDR = new ByteArray();
        IHDR.writeInt(width);
        IHDR.writeInt(height);
		IHDR.writeByte(8); // bit depth per channel
		IHDR.writeByte(6); // color type: RGBA
		IHDR.writeByte(0); // compression method
		IHDR.writeByte(0); // filter method
        IHDR.writeByte(0); // interlace method
        writeChunk(0x49484452, IHDR);

        // Build IDAT chunk
        IDAT = new ByteArray();
        zPNG =new ByteArray();
        
        zStream.next_in=IDAT;
        zStream.next_in_index=0;
        
        zStream.next_out=zPNG;
        zStream.next_out_index=0;
        
        y=0;
    }
    
    private function loopEncode():void
    {
    	//async
    	if(loopTime>runTime)
    	{
	    	loopTime=0;
	    	setTimeout(loopEncode, haltTime);
	    	return;
    	}
    	
    	//encode
    	if (y < height )
        {
        	var time:int=getTimer();
        	
            IDAT.writeByte(0); // no filter

            var x:int;
            var pixel:uint;
            
			if (!transparent)
            {
                for (x = 0; x < width; x++)
                {
                    if (sourceBitmapData)
                    	pixel = sourceBitmapData.getPixel(x, y);
                   	else
             			pixel = sourceByteArray.readUnsignedInt();
					
					IDAT.writeUnsignedInt(uint(((pixel & 0xFFFFFF) << 8) | 0xFF));
                }
            }
            else
            {
                for (x = 0; x < width; x++)
                {
                    if (sourceBitmapData)
                   		pixel = sourceBitmapData.getPixel32(x, y);
                    else
						pixel = sourceByteArray.readUnsignedInt();
 
                    IDAT.writeUnsignedInt(uint(((pixel & 0xFFFFFF) << 8) |
												(pixel >>> 24)));
                }
            }
            
            //compress new IDAT bytes
            zPNG.length+=4*width;
            
            zStream.avail_in=zStream.avail_out=4*width;
            zStream.deflate(FZlib.Z_NO_FLUSH);
            
            //TODO
            zPNG.bytesAvailable;
            
            //progress
            dispatchEvent(new EncodeProgressEvent(y,height));
            
            //next line
            y++;
            loopTime+=getTimer()-time;
            loopEncode();
        }
        else
        {
        	endEncode();
        }
    }
    
    private function endEncode():void
    {
    	zStream.deflate(FZlib.Z_FINISH);
    	zStream.deflateEnd();
    	
    	//for speed skip CRC
        writeChunk(0x49444154, zPNG, true);

        // Build IEND chunk
        writeChunk(0x49454E44, null);

        // return PNG
        png.position = 0;
        
        //triger event
        dispatchEvent(new EncodeCompleteEvent(png));
    }

    /**
	 *  @private
	 */
	private function writeChunk(type:uint, data:ByteArray, skipCRC:Boolean=false):void
    {
        // Write length of data.
        var len:uint = 0;
        if (data)
            len = data.length;
		png.writeUnsignedInt(len);
        
		// Write chunk type.
		var typePos:uint = png.position;
		png.writeUnsignedInt(type);
        
		// Write data.
		if (data)
            png.writeBytes(data);
		
		if(skipCRC)
		{
			 png.writeUnsignedInt(0);
			 return;
		}
        // Write CRC of chunk type and data.
		var crcPos:uint = png.position;
        png.position = typePos;
        var crc:uint = 0xFFFFFFFF;
        for (var i:uint = typePos; i < crcPos; i++)
        {
            crc = uint(crcTable[(crc ^ png.readUnsignedByte()) & uint(0xFF)] ^
					   uint(crc >>> 8));
        }
        crc = uint(crc ^ uint(0xFFFFFFFF));
        png.position = crcPos;
        png.writeUnsignedInt(crc);
    }
}

}
